/**
 * 
 */
package it.uniroma3.dia.digraph.algorithm.stackedTri;

import it.uniroma3.dia.digraph.algorithm.Algorithm;
import it.uniroma3.dia.digraph.algorithm.utility.Utility;
import it.uniroma3.dia.digraph.algorithm.utility.stackedtri.StackedTriGraph;
import it.uniroma3.dia.digraph.algorithm.utility.stackedtri.StackedTriNode;

import java.util.Collections;
import java.util.Comparator;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Map;

import org.graphdrawing.graphml.xmlns.GraphType;

/**
 * Morph a stacked triangulation into anothet by defining node position as a convex
 * combination of its "parents".
 * @author Cionzo
 *
 */
public class ConvexCoordMorphing extends Algorithm {
	
	private StackedTriGraph outerplanarSource;
	private Map<Integer, List<StackedTriNode>> sourceDepth2nodes, targetDepth2nodes;
	private ComparatorById comparator;

	/* (non-Javadoc)
	 * @see it.uniroma3.dia.digraph.algorithm.Algorithm#apply(org.graphdrawing.graphml.xmlns.GraphType[])
	 */
	@Override
	public void apply(GraphType... inputGraphs) {
		
		// iterate on level:
		//move nodes on current level to target position
		//and update position for nodes on higher levels
		
		for (int depth = 0; depth<this.sourceDepth2nodes.size(); depth++){
			
			moveToTargetPosition(depth);	

			updateHigherLevels(depth+1);

			// ... and show what's up
			setDefault(false, depth);
			this.updateView();
			setDefault(true, depth);
		}

	}

	private void setDefault(boolean defaultValue, int depth) {
		
		for (StackedTriNode node : this.sourceDepth2nodes.get(depth)){
			node.getNode().setDefault(defaultValue);
		}
		
	}

	/**
	 * Updates the position of the nodes at depth greater than, or equal to that specified
	 * @param depth the first level to update
	 */
	private void updateHigherLevels(int depth) {
		
		for (int j=depth; j<this.sourceDepth2nodes.size();j++){
			
			// for each node on this level
			for (StackedTriNode node : this.sourceDepth2nodes.get(j)){
				//parents' positions are up to date, just combine them
				node.updatePosition();
				
			}
		}
		
		
	}

	/**
	 * Move nodes at current depth to their target positions
	 * @param depth the depth of the nodes to move
	 */
	private void moveToTargetPosition(int depth) {
		
		//collections with source and target node for this level
		List<StackedTriNode> sourceNodes = this.sourceDepth2nodes.get(depth);
		List<StackedTriNode> targetNodes = this.targetDepth2nodes.get(depth);
		
		//sort both collections to have the nodes at same index
		Collections.sort(sourceNodes, this.comparator);
		Collections.sort(targetNodes, this.comparator);
		
		StackedTriNode source, target;
		
		for (int i=0;i<sourceNodes.size();i++){
			//get source and corresponding target node
			source = sourceNodes.get(i);
			target = targetNodes.get(i);
			
			//set source node position to target position
			source.getNode().setX(target.getNode().getX());
			source.getNode().setY(target.getNode().getY());
			
			//current node is at its target position: it's arrived
			source.setArrived(true);
		}		
	}

	/* (non-Javadoc)
	 * @see it.uniroma3.dia.digraph.algorithm.Algorithm#init(org.graphdrawing.graphml.xmlns.GraphType[])
	 */
	@Override
	protected void init(GraphType... inputGraphs) {
		
		this.setGraph(inputGraphs[0]);
		this.outerplanarSource = Utility.getOuterplanarGraph(inputGraphs[0]);
		
		this.sourceDepth2nodes = outerplanarSource.getNodes();
		this.targetDepth2nodes = Utility.getDepthToNodes(inputGraphs[1]);
		this.comparator = new ComparatorById();
	}

	@Override
	public void checkInput(GraphType[] inputGraphs)
			throws InputMismatchException {
		if (inputGraphs.length!=2)
			throw new InputMismatchException("input must contain source and target graph");
	}

}


